package cn.lingyangwl.agile.member.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.auth.AuthApi;
import cn.lingyangwl.agile.auth.model.entity.LocalUser;
import cn.lingyangwl.agile.auth.model.rqrs.CancelAccountReq;
import cn.lingyangwl.agile.member.assembly.MemberUserAssembly;
import cn.lingyangwl.agile.member.constant.AccountFromType;
import cn.lingyangwl.agile.member.constant.AccountType;
import cn.lingyangwl.agile.member.constant.MemberCacheKey;
import cn.lingyangwl.agile.member.manager.MemberUserManager;
import cn.lingyangwl.agile.member.mapper.MemberUserMapper;
import cn.lingyangwl.agile.member.model.entity.MemberLevel;
import cn.lingyangwl.agile.member.model.entity.MemberUser;
import cn.lingyangwl.agile.member.model.mq.MemberRegisterMQ;
import cn.lingyangwl.agile.member.model.mq.MemberUserDeleteMQ;
import cn.lingyangwl.agile.member.model.mq.MemberUserLevelUpgradeMQ;
import cn.lingyangwl.agile.member.model.rqrs.account_detail.AccountDetailSaveReq;
import cn.lingyangwl.agile.member.model.rqrs.user.*;
import cn.lingyangwl.agile.misc.statistic.*;
import cn.lingyangwl.agile.misc.statistic.model.*;
import cn.lingyangwl.agile.model.constants.AppType;
import cn.lingyangwl.agile.model.constants.UserCons;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.system.model.rqrs.user.UserStatusUpdateReq;
import cn.lingyangwl.framework.data.mate.annotations.FieldBindHandle;
import cn.lingyangwl.framework.lock.constant.LockBeanName;
import cn.lingyangwl.framework.lock.manager.ILockManager;
import cn.lingyangwl.framework.lock.model.ILock;
import cn.lingyangwl.framework.mybatis.plus.utils.PageUtil;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 会员用户
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class MemberUserServiceImpl extends ServiceImpl<MemberUserMapper, MemberUser>
    implements IService<MemberUser>, MemberUserService {
    
    @Resource
    private MemberUserAssembly memberUserAssembly;
    @Resource
    private MemberUserManager memberUserManager;
    @Resource
    private MemberLevelService levelService;
    @Resource
    private AuthApi authApi;
    @Resource
    private MemberMqService memberMqService;
    @Resource(name = LockBeanName.REDISSON_SERVICE)
    private ILockManager lockManager;
    @Resource
    @Lazy
    private AccountDetailService accountDetailService;
    @Resource
    private StatisticApi statisticApi;

    @Override
    public IPage<MemberUserResp> pageUser(MemberUserPageReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<MemberUser> lqw = MemberUser.lqw()
            .eq(StringUtils.isNotEmpty(req.getPhone()), MemberUser::getPhone, req.getPhone())
            .eq(StringUtils.isNotEmpty(req.getStatus()), MemberUser::getStatus, req.getStatus())
            .eq(StringUtils.isNotEmpty(req.getRegisterTerminal()), MemberUser::getRegisterTerminal, req.getRegisterTerminal())
            .eq(Objects.nonNull(req.getLevelId()), MemberUser::getLevelId, req.getLevelId())
            .ge(Objects.nonNull(beginTime), MemberUser::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), MemberUser::getCreatedAt, endTime)
            .orderByDesc(MemberUser::getCreatedAt);
        IPage<MemberUser> pageData = this.page(PageUtil.to(req), lqw);

        List<Long> levelIds = pageData.getRecords().stream().map(MemberUser::getLevelId)
                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
        levelIds.add(-1L);

        Map<Long, MemberLevel> levelMap = levelService.listByIds(levelIds).stream()
                .collect(Collectors.toMap(MemberLevel::getId, Function.identity()));

        MemberLevel emptyLevel = new MemberLevel();
        return PageUtil.to(pageData, (e) -> {
            MemberUserResp resp = memberUserAssembly.toUserResp(e);
            resp.setLevelName(levelMap.getOrDefault(e.getLevelId(), emptyLevel).getName());
            return resp;
        });
    }

    @Override
    @FieldBindHandle
    @Transactional(rollbackFor = Exception.class)
    public MemberUserResp getUser(Long id) {
        MemberUser entity = this.getById(id);
        MemberUserResp resp = memberUserAssembly.toUserResp(entity);
        if (Objects.nonNull(resp) && Objects.nonNull(resp.getLevelId())) {
            String name = Optional.ofNullable(levelService.getById(resp.getLevelId())).map(MemberLevel::getName)
                    .orElse("未知");
            resp.setLevelName(name);
        }

        // 判断用户是否需要升级, 使用分布式锁, 避免重复发放升级奖励
        MemberLevel nextLevel = levelService.getNextLevelIfUpgrade(entity);
        if (Objects.nonNull(nextLevel)) {
            String lockKey = MemberCacheKey.UPDATE_USER_LEVEL.formatKey(id);
            ILock lock = lockManager.getLock(lockKey);
            if (lock.isLocked()) {
                return resp;
            }
            try {
                lock.lock();

                // 二次判断
                entity = this.getById(entity.getId());
                MemberLevel lastNextLevel = levelService.getNextLevelIfUpgrade(entity);
                if (Objects.isNull(lastNextLevel)) {
                    return resp;
                }

                entity.setLevelId(lastNextLevel.getId());
                updateById(entity);

                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        memberMqService.send(MemberUserLevelUpgradeMQ.build(id, lastNextLevel));
                    }
                });
            } finally {
                lock.unlock();
            }
        }
        return resp;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveUser(MemberUserSaveReq req) {
        MemberUser entity = memberUserAssembly.toUser(req);
        boolean ret = this.save(entity);
        if (!ret) {
            throw new BizException("新增失败");
        }

        LocalUser localUser = LocalUser.builder()
                .userId(entity.getId()).userName(entity.getUserName()).password(req.getPassword())
                .phone(entity.getPhone()).email(req.getEmail()).appType(AppType.APP.getCode())
                .build();
        Long authId = authApi.saveLocalUser(localUser);
        if (Objects.isNull(authId)) {
            throw new BizException("新增失败-[授权异常]");
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUser(MemberUserUpdateReq req) {
        MemberUser oldUser = this.getById(req.getId());
        MemberUser newUser = memberUserAssembly.toUser(req);
        String lockKey = MemberCacheKey.UPDATE_USER_LEVEL.formatKey(newUser.getId());
        ILock lock = lockManager.getLock(lockKey);

        try {
            boolean tryLock = lock.tryLock(5, TimeUnit.SECONDS);
            if (!tryLock) {
                throw new BizException("变更会员等级失败");
            }
            // 如果不为空说明来自于管理端, 变更会员等级
            if (Objects.nonNull(req.getLevelId()) && oldUser.getLevelId().compareTo(newUser.getLevelId()) != 0) {
                MemberLevel nextLevel = levelService.getById(req.getLevelId());

                newUser.setLevelId(nextLevel.getId());
                newUser.setExperience(oldUser.getExperience() + nextLevel.getUpgrade().getExperience());

                boolean ret = this.updateById(newUser);
                if (!ret) {
                    throw new BizException("更新失败");
                }

                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        memberMqService.send(MemberUserLevelUpgradeMQ.build(newUser.getId(), nextLevel));
                    }
                });
            } else {
                boolean ret = this.updateById(newUser);
                if (!ret) {
                    throw new BizException("更新失败");
                }
            }

            LocalUser localUser = LocalUser.builder()
                    .userId(newUser.getId()).userName(newUser.getUserName()).password(req.getPassword())
                    .phone(newUser.getPhone()).email(req.getEmail())
                    .build();
            authApi.updateLocalUser(localUser);
        } finally {
            lock.unlock();
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteUser(Long id) {
        boolean ret = this.removeById(id);
        if (!ret) {
            throw new BizException("删除失败");
        }
        authApi.deleteLocalUser(id);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                memberMqService.send(MemberUserDeleteMQ.build(id));
            }
        });
        return true;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeleteUser(List<Long> ids) {
        boolean ret = this.removeByIds(ids);
        if (!ret) {
            throw new BizException("删除失败");
        }
        authApi.batchDeleteLocalUser(TenantContext.get().getTenantId(), ids);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                ids.forEach(e -> memberMqService.send(MemberUserDeleteMQ.build(e)));
            }
        });
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MemberUser registerUserIfAbsent(MemberUserRegisterReq req) {
        MemberUser dbUser = memberUserManager.getByPhone(req.getPhone());
        if (Objects.nonNull(dbUser)) {
            return dbUser;
        }

        String password = IdUtil.fastSimpleUUID();

        MemberUser user = new MemberUser();
        user.setSex(req.getSex());
        user.setUserName(req.getUserName());
        user.setNickName(req.getNickName());
        user.setRealName(StrUtil.emptyToDefault(req.getRealName(), req.getNickName()));
        user.setAvatar(req.getAvatar());
        user.setRegisterIp(req.getRegisterIp());
        user.setRegisterTerminal(req.getRegisterTerminal());
        user.setStatus(UserCons.Status.NORMAL);
        user.setPhone(req.getPhone());
        user.setLevelId(levelService.getDefaultLevel().getId());
        this.save(user);

        // 保存用户基本信息到授权中心
        LocalUser localUser = LocalUser.builder().userId(user.getId())
                .userName(user.getUserName()).phone(req.getPhone())
                .appType(AppType.APP.getCode()).status(user.getStatus())
                .password(password).build();
        authApi.saveLocalUser(localUser);

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                DayDimStatReq statReq = new DayDimStatReq();
                statReq.setMemberRegisterCount(1);
                statisticApi.sendMsg(statReq);
                memberMqService.send(MemberRegisterMQ.build(user));
            }
        });
        return user;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitCancel(LoginUser user) {
        // TODO 展示无需管理员审批, 直接注销
        // step 注销本地账号
        if (!this.removeById(user.getUserId())) {
            throw new BizException("注销失败");
        }

        // step 注销授权中心相关账号
        CancelAccountReq cancelAccountReq = new CancelAccountReq()
                .setUserId(user.getUserId()).setAppType(user.getAppType());
        authApi.cancelAccount(cancelAccountReq);

        // step 当注销成功, 可以发送消息来删除用户的相关数据

        // step 注销成功 发送删除消息
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                DayDimStatReq statReq = new DayDimStatReq();
                statReq.setMemberCancelCount(1);
                statisticApi.sendMsg(statReq);
                memberMqService.send(MemberUserDeleteMQ.build(user.getUserId()));
            }
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchUserStatus(UserStatusUpdateReq req) {
        List<Long> userIds = StringUtils.toList(req.getIds(), ",", Long.class);

        LambdaQueryWrapper<MemberUser> lqw = MemberUser.lqw().in(MemberUser::getId, userIds);
        MemberUser user =  new MemberUser();
        user.setStatus(req.getStatus());
        if (!this.update(user, lqw)) {
            throw new BizException("用户状态更新失败, 请联系管理员");
        }

        // 更新状态之后, 删除用户登录状态
        userIds.forEach(authApi::removeAccessTokenByUserId);

        List<LocalUser> localUserList = userIds.stream()
                .map(e -> LocalUser.builder().userId(e).status(req.getStatus()).build())
                .collect(Collectors.toList());
        authApi.batchUpdateLocalUser(localUserList);
    }

    @Override
    public void updatePoint(MemberUser user, BigDecimal accountData) {
        if (Objects.isNull(accountData) || Objects.isNull(user)) {
            log.warn("user or accountData is null, no update point");
            return;
        }
        if ((user.getPoint() + accountData.intValue()) < 0) {
            log.warn("积分不能小于0, user: {}, accountData: {}", JSON.toJSONString(user), accountData);
            return;
        }
        LambdaUpdateWrapper<MemberUser> luw = MemberUser.luw().in(MemberUser::getId, user.getId())
                .setSql("point = point + " + accountData.toString());
        if (!this.update(luw)) {
            throw new BizException("更新用户积分失败");
        }
    }

    @Override
    public void updateExperience(MemberUser user, BigDecimal accountData) {
        if (Objects.isNull(accountData)) {
            return;
        }
        if ((user.getPoint() + accountData.intValue()) < 0) {
            log.warn("成长值不能小于0, user: {}, accountData: {}", JSON.toJSONString(user), accountData);
            return;
        }
        LambdaUpdateWrapper<MemberUser> luw = MemberUser.luw().in(MemberUser::getId, user.getId())
                .setSql("experience = experience + " + accountData.intValue());
        if (!this.update(luw)) {
            throw new BizException("更新用户成长值失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustPoint(Long userId, Integer point, String remark) {
        if (Objects.isNull(point)) {
            return;
        }

        // 保存调整信息
        AccountDetailSaveReq saveReq = AccountDetailSaveReq.builder()
                .accountData(new BigDecimal(point)).accountType(AccountType.POINT)
                .user(this.getById(userId))
                .remark(remark).fromType(AccountFromType.ADJUST).build();
        accountDetailService.saveAccountDetail(saveReq);
    }
}
